/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.editpart;

import java.util.Iterator;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.XYLayout;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPolicy;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.gef.tools.CellEditorLocator;
import org.eclipse.gef.tools.DirectEditManager;
import org.eclipse.ui.views.properties.IPropertySource;
import org.eclipse.uml2.uml.Behavior;
import org.eclipse.uml2.uml.BehavioralFeature;
import org.eclipse.uml2.uml.ConnectionPointReference;
import org.eclipse.uml2.uml.Constraint;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.ElementImport;
import org.eclipse.uml2.uml.LiteralString;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Pseudostate;
import org.eclipse.uml2.uml.Region;
import org.eclipse.uml2.uml.State;

import com.cea.papyrus.core.commands.GraphNodeSetConstraintCommand;
import com.cea.papyrus.core.directedit.ILabelControler;
import com.cea.papyrus.core.directedit.LabelCellEditorLocator;
import com.cea.papyrus.core.directedit.LabelControler;
import com.cea.papyrus.core.directedit.LabelDirectEditManager;
import com.cea.papyrus.core.directedit.PropertyAccessor;
import com.cea.papyrus.core.editor.ModelManager;
import com.cea.papyrus.core.editpart.ContainerHighlightEditPolicy;
import com.cea.papyrus.core.editpart.ContainerWithSemanticEditPart;
import com.cea.papyrus.core.editpart.DimensionAdaptationEditPolicy;
import com.cea.papyrus.core.ui.properties.advanced.EObjectPropertySource;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagram.statemachine.figure.StateFigure;
import com.cea.papyrus.diagram.statemachine.helper.NodeStateHelper;
import com.cea.papyrus.diagraminterchange2.di2.Diagram;
import com.cea.papyrus.diagraminterchange2.di2.GraphConnector;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Property;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * Controler for State in state machine diagrams.
 * 
 * @author David Servat
 * @since 29 jan. 07
 */
public class StateEditPart extends ContainerWithSemanticEditPart{

	/**
	 * The channel.
	 */
	private int channel = 501;

	/**
	 * Manager for editing classname.
	 */
	private DirectEditManager manager;

	/**
	 * The constructor.
	 * 
	 * @param modelManager the associated ModelManager
	 * @param graphNode the GraphNode to be managed
	 */
	public StateEditPart(GraphNode graphNode, ModelManager modelManager){
		super(graphNode, modelManager);

		Debug.debug(this,"call StateEditPart(GraphNode graphnode, ModelManager modelManager)",channel);
	}

	/**
	 * Specific edit policy installation.
	 * 
	 * @see com.cea.papyrus.common.editpart.ContainerWithSemanticEditPart#createEditPolicies()
	 */
	@Override
	protected void createEditPolicies(){
		super.createEditPolicies();

		installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE, new ContainerHighlightEditPolicy());
		installEditPolicy(EditPolicy.LAYOUT_ROLE, new StateMachineLayoutEditPolicy((XYLayout)getContentPane().getLayoutManager()));
        // AdaptDimensionEditPolicy
        installEditPolicy(DimensionAdaptationEditPolicy.DIMENSION_ADAPTATION, new ContainerDimensionAdaptationEditPolicy());
	}
	

	/**
	 * Calls for the appropriate figure associated to the element.
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.editpart.GraphNodeWithSemanticEditPart#createFigure()
	 */
	@Override
	protected IFigure createFigure(){
		return new StateFigure();
	}
	
	/**
	 * This method dynamically calls child EditPart during the construction chain
	 * Here this is needed because some nodes - i.e. splitters - are not linked to any UML elements.
	 * 
	 * @param model 
	 * 
	 * @return 
	 * 
	 * @see org.eclipse.gef.editparts.AbstractEditPart#createChild(java.lang.Object)
	 */
	@Override
	protected EditPart createChild(Object model){
		if(model instanceof GraphNode){
			EditPart part = null;
			//there are two possible cases
			//either we have a splitter node or a region node
			//the splitter node has no semantic model
			Uml1SemanticModelBridge semanticModel = (Uml1SemanticModelBridge)((GraphNode)model).getSemanticModel();
			if(semanticModel == null){
				part = new SplitterEditPart((GraphNode)model, modelManager);
			}
			else if(semanticModel.getElement() instanceof Region){
				part = new RegionEditPart((GraphNode)model, modelManager);
			}
			else if(semanticModel.getElement() instanceof Pseudostate){
				part = new PseudostateEditPart((GraphNode)model, modelManager);
			}
			else if(semanticModel.getElement() instanceof ConnectionPointReference){
				part = new ConnectionPointReferenceEditPart((GraphNode)model, modelManager);
			}
			part.setModel(model);
			return part;
		}
		return super.createChild(model);
	}


	/**
	 * Returns the associated UML element.
	 * 
	 * @return 
	 * 
	 * @see com.cea.papyrus.common.editpart.GraphNodeWithSemanticEditPart#getUmlElement()
	 */
	@Override
	public State getUmlElement(){
		Element e = ((Uml1SemanticModelBridge)getGraphNode().getSemanticModel()).getElement();
        // Case of element import
        if(e instanceof ElementImport)
        	return (State)((ElementImport)e).getImportedElement();
		return (State)e;
	}

	/**
	 * Helper method to translate all subnodes of a composite state.
	 * 
	 */
	private CompoundCommand translateSubnodes(GraphNode g, int height){
		Debug.debug(this, "Call StateEditPart.translateSubnodes(GraphNode g, int height)", channel);
		
		CompoundCommand command = new CompoundCommand();

		Iterator it = g.getSemanticChild().iterator();
		while(it.hasNext()){
			GraphNode current = (GraphNode)it.next();
			//test to avoid Entry/Exit points which are semantic children
			if((current.getSemanticModel() == null) || (((Uml1SemanticModelBridge)current.getSemanticModel()).getElement() instanceof Region)){
				GraphNodeSetConstraintCommand unitCommand = new GraphNodeSetConstraintCommand(current, new ChangeBoundsRequest(), new Rectangle(current.getPosition().getTranslated(0, height), current.getSize().getCopy()));
				command.add(unitCommand);
			}
		}
		return command;
	}
	
	/**
	 * Helper method to translate anchors on the lower border of a composite state.
	 * 
	 */
	private void translateAnchors(GraphNode g, int height){
		Debug.debug(this, "Call StateEditPart.translateAnchors(GraphNode g, int height)", channel);
		
		Iterator it = g.getAnchorage().iterator();
		while(it.hasNext()){
			GraphConnector current = (GraphConnector)it.next();
			if(current.getPosition().y == (g.getPosition().y+g.getSize().height))
				current.getPosition().translate(0, height);
		}
	}
	
	/**
	 * This method is called whenever graphical changes need to be performed.
	 * 
	 * @see org.eclipse.gef.editparts.AbstractEditPart#refreshVisuals()
	 */
	@Override
	protected void refreshVisuals(){
		Dimension size = getGraphNode().getSize();
		Point p = getGraphNode().getPosition();
		Rectangle r = new Rectangle(p, size);

		StateFigure f = (StateFigure)getFigure();
		String fullName = getUmlElement().getName();
		if(getUmlElement().isSubmachineState())
			fullName += ":"+getUmlElement().getSubmachine().getName();		
		if(getUmlElement().getStateInvariant() != null){
			Constraint stInv = getUmlElement().getStateInvariant();
			if(stInv.getSpecification() instanceof LiteralString){
				LiteralString ls = (LiteralString)stInv.getSpecification();
				fullName += "\n"+ls.stringValue();
			}
		}
		f.setName(fullName);
		
		//colors
		f.refreshColors(getGraphNode());

		//look for list of stereotypes
		f.refreshPresentation(getPresentation(), getUmlElement(), getGraphNode());
		com.cea.papyrus.umlutils.Element currentElement = new com.cea.papyrus.umlutils.Element(getUmlElement());
		boolean hasIcon  = currentElement.hasIcon();
		boolean hasShape = currentElement.hasShape();

		f.refreshStereotypes(f.stereotypesLabel(this), getPresentation(), hasIcon, hasShape);
		
		//qualifiedName
		GraphNode gp = (GraphNode)getGraphNode().getContainer();
		Element graphicalumlParent = null;
		if(gp != null){
			if(gp instanceof Diagram){
				if(((Diagram) gp).getOwner() != null){
					graphicalumlParent = ((Uml1SemanticModelBridge)((Diagram) gp).getOwner()).getElement();
				}
			} 
			else{
				if(gp.getSemanticModel() != null){
					graphicalumlParent = ((Uml1SemanticModelBridge)gp.getSemanticModel()).getElement();
				}
			}
		}
		if(graphicalumlParent != null){
			if(!(graphicalumlParent.equals(getUmlElement().getOwner()))){
				f.setQualifiedName(getUmlElement().getQualifiedName());
			}
		}
	
		//extra info on states
		//entry
		Property prop = getGraphNode().getProperty(NodeStateHelper.STATE_DISPLAY_ENTRY);
		if(prop != null){
			if(prop.getValue().equals(NodeStateHelper.STATE_DISPLAY)){
				if(((State)getUmlElement()).getEntry() != null){
					f.setShowEntry(true);
					Behavior behavior = ((State)getUmlElement()).getEntry();
					BehavioralFeature specification = behavior.getSpecification();
					if(specification != null){
						if(specification instanceof Operation){
							f.setEntryText(com.cea.papyrus.umlutils.Operation.getSignature((Operation)specification, false));
						}
						else{
							f.setEntryText(specification.getName());
						}
					}
					else
						f.setEntryText(behavior.getName());
				}
				else{
					prop.setValue(NodeStateHelper.STATE_NOT_DISPLAY);
					f.setShowEntry(false);
				}
			}
			else
				f.setShowEntry(false);
		}
		//exit
		prop = getGraphNode().getProperty(NodeStateHelper.STATE_DISPLAY_EXIT);
		if(prop != null){
			if(prop.getValue().equals(NodeStateHelper.STATE_DISPLAY)){
				if(((State)getUmlElement()).getExit() != null){
					f.setShowExit(true);
					Behavior behavior = ((State)getUmlElement()).getExit();
					BehavioralFeature specification = behavior.getSpecification();
					if(specification != null){
						if(specification instanceof Operation){
							f.setExitText(com.cea.papyrus.umlutils.Operation.getSignature((Operation)specification, false));
						}
						else{
							f.setExitText(specification.getName());
						}
					}
					else
						f.setExitText(behavior.getName());
				}
				else{
					prop.setValue(NodeStateHelper.STATE_NOT_DISPLAY);
					f.setShowExit(false);
				}
			}
			else
				f.setShowExit(false);
		}
		//do activity
		prop = getGraphNode().getProperty(NodeStateHelper.STATE_DISPLAY_DO_ACTIVITY);
		if(prop != null){
			if(prop.getValue().equals(NodeStateHelper.STATE_DISPLAY)){
				if(((State)getUmlElement()).getDoActivity() != null){
					f.setShowDoActivity(true);
					Behavior behavior = ((State)getUmlElement()).getDoActivity();
					BehavioralFeature specification = behavior.getSpecification();
					if(specification != null){
						if(specification instanceof Operation){
							f.setDoActivityText(com.cea.papyrus.umlutils.Operation.getSignature((Operation)specification, false));
						}
						else{
							f.setDoActivityText(specification.getName());
						}
					}
					else
						f.setDoActivityText(behavior.getName());
				}
				else{
					prop.setValue(NodeStateHelper.STATE_NOT_DISPLAY);
					f.setShowDoActivity(false);
				}				
			}
			else
				f.setShowDoActivity(false);
		}
		//deferrable triggers
		prop = getGraphNode().getProperty(NodeStateHelper.STATE_DISPLAY_DEFERRABLE_TRIGGERS);
		if(prop != null){
			if(prop.getValue().equals(NodeStateHelper.STATE_DISPLAY)){
				f.setShowDeferrableTriggers(true);
				int n = ((State)getUmlElement()).getDeferrableTriggers().size();
				if(n != 0){
					f.setNumberOfDeferrableTriggers(n);
					f.setDeferrableTriggersText(((State)getUmlElement()).getDeferrableTriggers().iterator());
				}
				else 
					f.setShowDeferrableTriggers(false);
			}
			else{
				f.setShowDeferrableTriggers(false);
				f.setNumberOfDeferrableTriggers(0);
			}
		}
		//composite
		boolean showComposite = false;
		if(getUmlElement().isComposite()){
			if(getGraphNode().getSemanticChild() != null){
				Iterator it = getGraphNode().getSemanticChild().iterator();
				int n = getGraphNode().getSemanticChild().size();
				while(it.hasNext()){
					GraphNode gn = (GraphNode)it.next();
					//test to avoid Entry/Exit points and stereotype properties which are semantic children
					if(((gn.getSemanticModel() == null) && (gn.getContained() != null) && !gn.getContained().isEmpty()) 
							|| ((gn.getSemanticModel() != null) && (((Uml1SemanticModelBridge)gn.getSemanticModel()).getElement() instanceof Pseudostate)))
						n--;
				}
				if(n != 0){
					f.setShowComposite(true);
					showComposite = true;
				}
				else
					f.setShowComposite(false);
			}
			else
				f.setShowComposite(false);
		}
		f.refreshFigure();
		
		if(showComposite){
			int limitHeight = f.getCurrentInformationFigureHeight()+f.getCurrentHeadHeight();
			Iterator it = getGraphNode().getSemanticChild().iterator();
			int actualCompositeHeight = getGraphNode().getPosition().y+getGraphNode().getSize().height;
			while(it.hasNext()){
				GraphNode gn = (GraphNode)it.next();
				//test to avoid Entry/Exit points which are semantic children
				if(((gn.getSemanticModel() == null) && (gn.getContained() != null) && !gn.getContained().isEmpty()) 
						|| ((gn.getSemanticModel() != null) && (((Uml1SemanticModelBridge)gn.getSemanticModel()).getElement() instanceof Region)))
					actualCompositeHeight = Math.min(actualCompositeHeight, gn.getPosition().y);
			}
			if(actualCompositeHeight != limitHeight){
				int dh = limitHeight-actualCompositeHeight; 
				CompoundCommand command = translateSubnodes(getGraphNode(), dh);
				translateAnchors(getGraphNode(), dh);
				//execute command
				//NB: the command is executed at once with no undo possible so that it appears to be chained with the command that triggered this call to refreshVisuals
				//in the StatePresentationSection widgets!!
				//it cannot do to simply disable the undo of compound command and execute it via the CommandStack because in that case the chain of undos is broken
				command.execute();
				//expand container state
				size.height += dh;
				r.setSize(size);
			}

		}

		((GraphicalEditPart)getParent()).setLayoutConstraint(this, f, r);
	}

	/**
	 * 
	 * 
	 * @return 
	 * 
	 * @see com.ibm.itso.sal330r.gefdemo.edit.NetworkElementEditPart#getPropertySource()
	 */
	@Override
	protected IPropertySource getPropertySource(){
		if (propertySource == null) {
			propertySource = new EObjectPropertySource(this.getUmlElement());
		}
		return propertySource;

	}

	/**
	 * Returns the Figure of this as a PackageFigure.
	 * 
	 * @return PackageFigure of this.
	 */
	protected StateFigure getPackageBoardFigure(){
		return (StateFigure)getFigure();
	}

	/**
	 * 
	 * 
	 * @return 
	 * 
	 * @see org.eclipse.gef.editparts.AbstractGraphicalEditPart#getContentPane()
	 */
	@Override
	public IFigure getContentPane(){
		return getPackageBoardFigure().getContentsPane();
	}

	/**
	 * Returns the Name Label of this edit part.
	 * 
	 * @return Label
	 */
	protected Label getNameLabel(){
		StateFigure fig = (StateFigure)getFigure();
		return fig.getNameLabel();
	}

	/**
	 * Init the label direct edit manager.
	 * This method should be called when a Direct Edit request is performed.
	 */
	protected void initLabelEditManager(){
		if(manager == null){
			//locator for the cell editor (on the name label)
			CellEditorLocator locator = new LabelCellEditorLocator(getNameLabel());

			//defining how properties are modified in the model
			PropertyAccessor accessor = new PropertyAccessor(){
				public String getPropertyName(){
					return "Name";
				}
				public String getValue(){
					return getUmlElement().getName();
				}
				public void setValue(String value){
					getUmlElement().setName(value);  
				}
			};
			//defines the controler: link between model and view
			ILabelControler labelControler = new LabelControler(getNameLabel(), accessor);

			//creates the editing manager.
			manager = new LabelDirectEditManager(this, locator, labelControler);
		}
	}

	/**
	 * the direct edit mechanism requires the edit part to handle the direct edit
	 * request.
	 * 
	 * @param req the request that called the direct edit.
	 */
	public void performDirectEdit(Request req){
		initLabelEditManager();
		manager.show(); 
	}

	/**
	 * 
	 * 
	 * @param request 
	 * 
	 * @see org.eclipse.gef.editparts.AbstractEditPart#performRequest(org.eclipse.gef.Request)
	 */
	@Override
	public void performRequest(Request request){
		if(request.getType() == RequestConstants.REQ_DIRECT_EDIT){
			performDirectEdit(request);
		}
	}
}
